﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
using BTv2.Controls;

namespace BTv2.Model
{
    public class Rule : INotifyPropertyChanged
    {


        public event PropertyChangedEventHandler PropertyChanged;

        // This method is called by the Set accessor of each property. 
        // The CallerMemberName attribute that is applied to the optional propertyName 
        // parameter causes the property name of the caller to be substituted as an argument. 
        private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        private Condition _condition;

        public int ID { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public ObservableCollection<Action> Actions { get; set; }
        public ObservableCollection<Action> BackupActions { get; set; }
        public Condition Condition
        {
            get { return _condition; }
            set
            {
                if (_condition != value)
                {
                    _condition = value;
                    NotifyPropertyChanged();
                }
            }
        }
        public Condition BackupCondition { get; set; }
        private bool _enabled = false;
        public bool Enabled { get {
            return _enabled;
        }
            set
            {
                if (_enabled != value)
                {
                    _enabled = value;
                    NotifyPropertyChanged();
                    if (_enabled)
                    {
                        PeriodiEvaluationTimer = new Timer((1000 * 60 * 60 * 24) / DailyExecutionCount);
                        PeriodiEvaluationTimer.Elapsed += _refreshTimer_Elapsed;
                        PeriodiEvaluationTimer.AutoReset = true;
                        PeriodiEvaluationTimer.Enabled = true;
                    }
                    else
                    {
                        PeriodiEvaluationTimer.Stop();
                        PeriodiEvaluationTimer = null;
                    }
                }
            }
        }
        public int DailyExecutionCount { get; set; }
        public Timer PeriodiEvaluationTimer { get; set; }


        public Rule( int id, string name, string description, string actionids, string condition, bool enabled, int dailyexecutioncount)
        {
            ID = id;
            Name = name;
            Description = description;
            try
            {
                var testv = actionids.Split(' ');
                Actions = RulesController.GetInstance().GetActions(actionids.Split(' ').Where(ek=>ek != "").Select(x => int.Parse(x)).ToList());
            }
            catch
            {
                Actions = new ObservableCollection<Action>();
            }
            try
            {
                int pos = 0;
                var cc = condition.Split(' ').ToList();
                Condition = ParseSQLCondition(ref cc, ref pos);
                _enabled = enabled;
                DailyExecutionCount = dailyexecutioncount;
                if (_enabled)
                {
                    PeriodiEvaluationTimer = new Timer((1000 * 60 * 60 * 24) / DailyExecutionCount);
                    PeriodiEvaluationTimer.Elapsed += _refreshTimer_Elapsed;
                    PeriodiEvaluationTimer.AutoReset = true;
                    PeriodiEvaluationTimer.Enabled = true;
                }
            
           }
            catch{
            }
        }

        
        void _refreshTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            Task.Run(async() => {
                bool result = await EvaluateRule();
                RulesController.GetInstance().SaveRuleResult(this, result);
                if (result) ExecuteRule();
            
            });
          

        }

        public async Task<bool> EvaluateRule()
        {
            return await Condition.EvaluateCondition();
        }
        public Rule()
        {
            Actions = new ObservableCollection<Action>();
        }

        public void ExecuteRule()
        {
            foreach (var action in Actions)
            {
                action.Execute();
            }
        }

        public string GetSQLCondition()
        {
            return "select (" + Condition.ParseSQLCondition()+")";
        }

        private Condition ParseSQLSCondition(ref List<string> sqlstring, ref int position)
        {
            SimpleCondition sc = new SimpleCondition();
            int start = sqlstring[position].LastIndexOf("(");
            int end = sqlstring[position].IndexOf(")");
            string sid = sqlstring[position].Substring(start + 1, end - start - 1);
            int id = int.Parse(sid);
            sc.Sensor = SensorsController.GetInstance().Sensors.Where(s => s.ID == id).First();
            position++;
            sc.Operator = sqlstring[position];
            position++;
            start = sqlstring[position].IndexOf(")");
            double val = double.Parse(sqlstring[position].Substring(0, start));
            sc.Value = val;
            position++;
           return sc;
        }

        private Condition ParseSQLCCondition(ref List<string> sqlstring, ref int position)
        {
            ComplexCondition cc = new ComplexCondition();
            while ((sqlstring.ElementAt(position).Contains("((")))
            {
                sqlstring[position] = sqlstring[position].Replace("((", "(");
            }
            cc.LeftSideCondition = ParseSQLSCondition(ref sqlstring, ref position);
            cc.LeftSideCondition.Parent = cc;
            cc.Operator = sqlstring[position] == "AND" ? "&&" : "||";
            position++;
            if (sqlstring.ElementAt(position).Contains("(("))
            {
                cc.RightSideCondition = ParseSQLCCondition(ref sqlstring, ref position);
            }
            else
            {
                cc.RightSideCondition = ParseSQLSCondition(ref sqlstring, ref position);
            }
            cc.RightSideCondition.Parent = cc;
            return cc;
        }
        private Condition ParseSQLCondition(ref List<string> sqlstring, ref int position)
        {
            Condition c = null;
            while (position < sqlstring.Count)
            {
                if (sqlstring.ElementAt(position) == "select")
                {
                    position++;
                }
                else if (sqlstring.ElementAt(position).Contains("((") )
                {
                    try
                    {

                        ComplexCondition cc = new ComplexCondition();
                        while((sqlstring.ElementAt(position).Contains("(("))){
                        sqlstring[position] = sqlstring[position].Replace("((", "(");
                        }
                       
                        cc.LeftSideCondition = ParseSQLSCondition(ref sqlstring, ref position); 
                        cc.LeftSideCondition.Parent = cc;
                        cc.Operator = sqlstring[position] == "AND" ? "&&" : "||";
                        position++;
                        if (sqlstring.ElementAt(position).Contains("(("))
                        {
                            cc.RightSideCondition = ParseSQLCCondition(ref sqlstring, ref position);
                        }
                        else
                        {
                            cc.RightSideCondition = ParseSQLSCondition(ref sqlstring, ref position);
                        }
                        cc.RightSideCondition.Parent = cc;
                        c = cc;
                    }
                    catch
                    {

                    }
                }
                else if (sqlstring.ElementAt(position).Contains("(lastSensorValue")||sqlstring.ElementAt(position).Contains("lastSensorValue"))
                {
                    try
                    {
                        SimpleCondition sc = new SimpleCondition();
                        int start = sqlstring[position].LastIndexOf("(");
                        int end = sqlstring[position].IndexOf(")");
                        string sid = sqlstring[position].Substring(start+1, end - start - 1);
                        int id = int.Parse(sid);
                        sc.Sensor = SensorsController.GetInstance().Sensors.Where(s => s.ID == id).First();
                        position++;
                        sc.Operator = sqlstring[position];
                        position++;
                        start = sqlstring[position].IndexOf(")");
                        double val =double.Parse(sqlstring[position].Substring(0, start));
                        sc.Value = val;
                        position++;
                        c= sc;
                    }
                    catch
                    {

                    }
                }
                else if (sqlstring.ElementAt(position).Contains("AND") || sqlstring.ElementAt(position).Contains("OR"))
                {
                    try
                    {
                        ComplexCondition cc = new ComplexCondition();
                        cc.LeftSideCondition = c;
                        cc.LeftSideCondition.Parent = cc;
                        cc.Operator = sqlstring[position] == "AND" ? "&&" : "||";
                        position++;
                        if (sqlstring.ElementAt(position).Contains("(("))
                        {
                            cc.RightSideCondition = ParseSQLCCondition(ref sqlstring, ref position);
                        }
                        else
                        {
                            cc.RightSideCondition = ParseSQLSCondition(ref sqlstring, ref position);
                        }
                        cc.RightSideCondition.Parent = cc;
                        c = cc;
                    }
                    catch
                    {

                    }
                }
                else
                {
                    return c;
                }
            }
            return c;
        }

        public string GetActionIDS()
        {
            string ids = "";
            foreach (var ac in Actions)
            {
                ids += ac.ID + " ";
            }
            return ids;
        }

    }
}
